#***************************************************************************
#*                                                                         *
#*   Copyright (c) 2015                                                    *
#*   Yorik van Havre <yorik@uncreated.net>                                 *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU Lesser General Public License (LGPL)    *
#*   as published by the Free Software Foundation; either version 2 of     *
#*   the License, or (at your option) any later version.                   *
#*   for detail see the LICENCE text file.                                 *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU Library General Public License for more details.                  *
#*                                                                         *
#*   You should have received a copy of the GNU Library General Public     *
#*   License along with this program; if not, write to the Free Software   *
#*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
#*   USA                                                                   *
#*                                                                         *
#***************************************************************************

import FreeCAD, os, time, tempfile, base64, Draft
from PySide import QtCore, QtGui

if FreeCAD.GuiUp:
    import FreeCADGui
    from DraftTools import translate
else:
    def translate(ctxt,txt):
        return txt

__title__="FreeCAD Arch Server commands"
__author__ = "Yorik van Havre"
__url__ = "http://www.freecadweb.org"



# BIMSERVER ###########################################################



class _CommandBimserver:
    
    "the Arch Bimserver command definition"
    
    def GetResources(self):
        return {'Pixmap'  : 'Arch_Bimserver',
                'MenuText': QtCore.QT_TRANSLATE_NOOP("Arch_Bimserver","BIM server"),
                'ToolTip': QtCore.QT_TRANSLATE_NOOP("Arch_Bimserver","Connects and interacts with a BIM server instance")}

    def Activated(self):
        try:
            import requests
        except:
            FreeCAD.Console.PrintError(translate("Arch","requests python module not found, aborting. Please install python-requests\n"))
            return
        try:
            import json
        except:
            FreeCAD.Console.PrintError(translate("Arch","json python module not found, aborting. Please install python-json\n"))
        else:
            FreeCADGui.Control.showDialog(_BimServerTaskPanel())


class _BimServerTaskPanel:
    
    '''The TaskPanel for the BimServer command'''
    
    def __init__(self):
        self.form = FreeCADGui.PySideUic.loadUi(":/ui/BimServerTaskPanel.ui")
        self.form.setWindowIcon(QtGui.QIcon(":/icons/Arch_Bimserver.svg"))
        self.form.labelStatus.setText("")
        QtCore.QObject.connect(self.form.buttonServer, QtCore.SIGNAL("clicked()"), self.login)
        QtCore.QObject.connect(self.form.buttonBrowser, QtCore.SIGNAL("clicked()"), self.browse)
        QtCore.QObject.connect(self.form.comboProjects, QtCore.SIGNAL("currentIndexChanged(int)"), self.getRevisions)
        QtCore.QObject.connect(self.form.buttonOpen, QtCore.SIGNAL("clicked()"), self.openFile)
        QtCore.QObject.connect(self.form.buttonUpload, QtCore.SIGNAL("clicked()"), self.uploadFile)
        self.prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
        self.Projects = []
        self.Revisions = []
        self.RootObjects = Draft.getObjectsOfType(FreeCAD.ActiveDocument.Objects,"Site")+Draft.getObjectsOfType(FreeCAD.ActiveDocument.Objects,"Building")
        for o in self.RootObjects:
            self.form.comboRoot.addItem(o.Label)
        self.setLogged(False)
        url,token = self.getPrefs()
        if url and token:
            self.getProjects()
            
    def getStandardButtons(self):
        return int(QtGui.QDialogButtonBox.Close)

    def accept(self):
        FreeCADGui.Control.closeDialog()

    def getPrefs(self):
        url = self.prefs.GetString("BimServerUrl","http://localhost:8082")
        if hasattr(self,"token"):
            token = self.token
        else:
            token = self.prefs.GetString("BimServerToken","")
            if token:
                self.token = token
        return url,token
        
    def setLogged(self,logged):
        if logged:
            self.form.buttonServer.setText("Connected")
            self.form.buttonServer.setIcon(QtGui.QIcon(":/icons/edit_OK.svg"))
            self.form.buttonServer.setToolTip("Click to log out")
            self.Connected = True
        else:
            self.form.buttonServer.setText("Not connected")
            self.form.buttonServer.setIcon(QtGui.QIcon(":/icons/edit_Cancel.svg"))
            self.form.buttonServer.setToolTip("Click to log in")
            self.Connected = False

    def login(self):
        self.setLogged(False)
        self.form.labelStatus.setText("")
        if self.Connected:
            # if the user pressed logout, delete the token
            self.prefs.SetString("BimServerToken","")
        else:
            url,token = self.getPrefs()
            loginform = FreeCADGui.PySideUic.loadUi(":/ui/DialogBimServerLogin.ui")
            loginform.editUrl.setText(url)
            dlg = loginform.exec_()
            if dlg:
                url = loginform.editUrl.text()
                login = loginform.editLogin.text()
                passwd = loginform.editPassword.text()
                store = loginform.checkStore.isChecked()
                import requests, json
                self.form.labelStatus.setText("Logging in...")
                url2 = url + "/json"
                data = {'request': {'interface': 'AuthInterface', 'method': 'login', 'parameters': {'username': login, 'password': passwd}}}
                try:
                    resp = requests.post(url2,data = json.dumps(data))
                except:
                    FreeCAD.Console.PrintError(translate("Arch","Unable to connect to BimServer at")+" "+url+"\n")
                    self.form.labelStatus.setText(translate("Arch","Connection failed."))
                    return
                if resp.ok:
                    try:
                        token = resp.json()["response"]["result"]
                    except:
                        return
                    else:
                        if store:
                            self.prefs.SetString("BimServerUrl",url)
                            if token:
                                self.prefs.SetString("BimServerToken",token)
                        else:
                            self.prefs.SetString("BimServerToken","")
                        if token:
                            self.token = token
                            self.getProjects()
        self.form.labelStatus.setText("")

    def browse(self):
        url = self.prefs.GetString("BimServerUrl","http://localhost:8082")+"/apps/bimviews"
        if self.prefs.GetBool("BimServerBrowser",False):
            FreeCADGui.addModule("WebGui")
            FreeCADGui.doCommand("WebGui.openBrowser(\""+url+"\")")
        else:
            QtGui.QDesktopServices.openUrl(QtCore.QUrl(url, QtCore.QUrl.TolerantMode))

    def getProjects(self):
        self.setLogged(False)
        self.Projects = []
        self.form.labelStatus.setText("")
        import requests, json
        url,token = self.getPrefs()
        if url and token:
            self.form.labelStatus.setText(translate("Arch","Getting projects list..."))
            url += "/json"
            data = { "token": token, "request": { "interface": "SettingsInterface", "method": "getServerSettings", "parameters": { } } }
            try:
                resp = requests.post(url,data = json.dumps(data))
            except:
                FreeCAD.Console.PrintError(translate("Arch","Unable to connect to BimServer at")+" "+url[:-5]+"\n")
                self.form.labelStatus.setText(translate("Arch","Connection failed."))
                return
            if resp.ok:
                try:
                    name = resp.json()["response"]["result"]["name"]
                except:
                    pass # unable to get the server name
                else:
                    self.form.labelServerName.setText(name)
            data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getAllProjects", "parameters": { "onlyTopLevel": "false", "onlyActive": "true" } } }
            resp = requests.post(url,data = json.dumps(data))
            if resp.ok:
                try:
                    projects = resp.json()["response"]["result"]
                except:
                    FreeCAD.Console.PrintError(translate("Arch","Unable to get projects list from BimServer\n"))
                else:
                    self.setLogged(True)
                    self.form.comboProjects.clear()
                    for p in projects:
                        self.form.comboProjects.addItem(p["name"])
                    self.Projects = projects
                    self.form.comboProjects.setCurrentIndex(0)
                    self.getRevisions(0)
        self.form.labelStatus.setText("")

    def getRevisions(self,index):
        self.form.labelStatus.setText("")
        self.form.listRevisions.clear()
        self.Revisions = []
        import requests, json
        url,token = self.getPrefs()
        if url and token:
            url += "/json"
            if (index >= 0) and (len(self.Projects) > index):
                p = self.Projects[index]
                self.form.labelStatus.setText(translate("Arch","Getting revisions..."))
                for rev in p["revisions"]:
                    data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getRevision", "parameters": { "roid": rev } } }
                    resp = requests.post(url,data = json.dumps(data))
                    if resp.ok:
                        try:
                            name = resp.json()["response"]["result"]["comment"]
                            date = resp.json()["response"]["result"]["date"]
                        except:
                            pass # unable to get the revision
                        else:
                            date = time.strftime("%a %d %b %Y %H:%M:%S GMT", time.gmtime(int(date)/1000.0))
                            self.form.listRevisions.addItem(date+" - "+name)
                            self.Revisions.append(resp.json()["response"]["result"])
        self.form.labelStatus.setText("")

    def openFile(self):
        self.form.labelStatus.setText("")
        if (self.form.listRevisions.currentRow() >= 0) and (len(self.Revisions) > self.form.listRevisions.currentRow()):
            rev = self.Revisions[self.form.listRevisions.currentRow()]
            import requests, json
            url,token = self.getPrefs()
            if url and token:
                FreeCAD.Console.PrintMessage(translate("Arch","Downloading file from Bimserver...\n"))
                self.form.labelStatus.setText(translate("Arch","Checking available serializers..."))
                url += "/json"
                serializer = None
                for s in ["Ifc2x3tc1"]: # Ifc4 seems unreliable ATM, let's stick with good old Ifc2x3...
                    data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getSerializerByName", "parameters": { "serializerName": s } } }
                    resp = requests.post(url,data = json.dumps(data))
                    if resp.ok:
                        try:
                            srl = resp.json()["response"]["result"]
                        except:
                            pass # unable to get this serializer
                        else:
                            serializer = srl
                            break
                if not serializer:
                    FreeCAD.Console.PrintError(translate("Arch","Unable to get a valid serializer from the BimServer\n"))
                    return
                tf = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(), "Save the downloaded IFC file?", None, "IFC files (*.ifc)")
                if tf:
                    tf = tf[0]
                self.form.labelStatus.setText(translate("Arch","Downloading file..."))
                data = { "token": token, "request": { "interface": "ServiceInterface", "method": "downloadRevisions", "parameters": { "roids": [rev["oid"]], "serializerOid": serializer["oid"], "sync": "false" } } }
                resp = requests.post(url,data = json.dumps(data))
                if resp.ok:
                    try:
                        downloadid = resp.json()["response"]["result"]
                    except:
                        FreeCAD.Console.PrintError(translate("Arch","Unable to obtain a valid download for this revision from the BimServer\n"))
                        return
                data = { "token": token, "request": { "interface": "ServiceInterface", "method": "getDownloadData", "parameters": { "topicId": downloadid } } }
                resp = requests.post(url,data = json.dumps(data))
                if resp.ok:
                    try:
                        downloaddata = resp.json()["response"]["result"]["file"]
                    except:
                        FreeCAD.Console.PrintError(translate("Arch","Unable to download the data for this revision.\n"))
                        return
                    else:
                        FreeCAD.Console.PrintMessage(translate("Arch","Opening file...\n"))
                        self.form.labelStatus.setText(translate("Arch","Opening file..."))
                        if not tf:
                            tf = tempfile.mkstemp(suffix=".ifc")[1]
                        f = open(tf,"wb")
                        f.write(base64.b64decode(downloaddata))
                        f.close()
                        import importIFC
                        importIFC.open(tf)
        self.form.labelStatus.setText("")

    def uploadFile(self):
        self.form.labelStatus.setText("")
        if (self.form.comboProjects.currentIndex() >= 0) and (len(self.Projects) > self.form.comboProjects.currentIndex()) and (self.form.comboRoot.currentIndex() >= 0):
            project = self.Projects[self.form.comboProjects.currentIndex()]
            import requests, json
            url,token = self.getPrefs()
            if url and token:
                url += "/json"
                deserializer = None
                FreeCAD.Console.PrintMessage(translate("Arch","Saving file...\n"))
                self.form.labelStatus.setText(translate("Arch","Checking available deserializers..."))
                import ifcopenshell
                schema = ifcopenshell.schema_identifier.lower()
                data = { "token": token, "request": { "interface": "PluginInterface",  "method": "getAllDeserializers", "parameters": { "onlyEnabled": "true" } } }
                resp = requests.post(url,data = json.dumps(data))
                if resp.ok:
                    try:
                        for d in resp.json()["response"]["result"]:
                            if schema in d["name"].lower():
                                deserializer = d
                                break
                    except:
                        pass
                if not deserializer:
                    FreeCAD.Console.PrintError(translate("Arch","Unable to get a valid deserializer for the schema")+" "+schema+"\n")
                    return
                tf = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(), translate("Arch","Save the IFC file before uploading?"), None, translate("Arch","IFC files (*.ifc)"))
                if tf:
                    tf = tf[0]
                if not tf:
                    tf = os.path.join(tempfile._get_default_tempdir(),next(tempfile._get_candidate_names())+".ifc")
                import importIFC
                self.form.labelStatus.setText(translate("Arch","Saving file..."))
                importIFC.export([self.RootObjects[self.form.comboRoot.currentIndex()]],tf)
                f = open(tf,"rb")
                ifcdata = base64.b64encode(f.read())
                f.close()
                FreeCAD.Console.PrintMessage(translate("Arch","Uploading file to Bimserver...\n"))
                self.form.labelStatus.setText(translate("Arch","Uploading file..."))
                data = { "token": token, "request": { "interface": "ServiceInterface", "method": "checkin", "parameters": { "poid": project["oid"], "comment": self.form.editComment.text(), "deserializerOid": deserializer["oid"], "fileSize": os.path.getsize(tf), "fileName": os.path.basename(tf), "data": ifcdata, "merge": "false", "sync": "true" } } }
                resp = requests.post(url,data = json.dumps(data))
                if resp.ok:
                    if resp.json()["response"]["result"]:
                        FreeCAD.Console.PrintMessage(translate("Arch","File upload successful\n"))
                        self.getRevisions(self.form.comboProjects.currentIndex())
                    else:
                        FreeCAD.Console.PrintError(translate("Arch","File upload failed\n"))
        self.form.labelStatus.setText("")



# GIT ###########################################################



class _CommandGit:
    "the Arch Git Commit command definition"
    def GetResources(self):
        return {'Pixmap'  : 'Git',
                'MenuText': QtCore.QT_TRANSLATE_NOOP("Arch_Git","Git"),
                'ToolTip': QtCore.QT_TRANSLATE_NOOP("Arch_Git","Manages the current document with Git")}

    def Activated(self):
        f = FreeCAD.ActiveDocument.FileName
        if not f:
            FreeCAD.Console.PrintError(translate("Arch","This document is not saved. Please save it first.\n"))
            return
        try:
            import git
        except:
            FreeCAD.Console.PrintError(translate("Arch","The Python Git module was not found. Please install the python-git package.\n"))
            return
        try:
            repo = git.Repo(os.path.dirname(f))
        except:
            FreeCAD.Console.PrintError(translate("Arch","This document doesn't appear to be part of a Git repository.\n"))
            return
        else:
            FreeCADGui.Control.showDialog(_GitTaskPanel(repo))


class _GitTaskPanel:
    
    '''The TaskPanel for the Git command'''
    
    def __init__(self,repo):
        self.form = FreeCADGui.PySideUic.loadUi(":/ui/GitTaskPanel.ui")
        self.form.setWindowIcon(QtGui.QIcon(":/icons/Git.svg"))
        self.form.labelStatus.setText("")
        QtCore.QObject.connect(self.form.buttonRefresh, QtCore.SIGNAL("clicked()"), self.getFiles)
        QtCore.QObject.connect(self.form.buttonLog, QtCore.SIGNAL("clicked()"), self.getLog)
        QtCore.QObject.connect(self.form.buttonSelectAll, QtCore.SIGNAL("clicked()"), self.form.listFiles.selectAll)
        QtCore.QObject.connect(self.form.buttonDiff, QtCore.SIGNAL("clicked()"), self.getDiff)
        QtCore.QObject.connect(self.form.buttonCommit, QtCore.SIGNAL("clicked()"), self.commit)
        QtCore.QObject.connect(self.form.buttonPush, QtCore.SIGNAL("clicked()"), self.push)
        QtCore.QObject.connect(self.form.buttonPull, QtCore.SIGNAL("clicked()"), self.pull)
        self.repo = repo
        self.getRemotes()
        self.getFiles()

    def getStandardButtons(self):
        return int(QtGui.QDialogButtonBox.Close)

    def accept(self):
        FreeCADGui.Control.closeDialog()
        
    def getFiles(self):
        self.form.labelStatus.setText("")
        self.form.listFiles.clear()
        self.modified = self.repo.git.diff("--name-only").split()
        self.untracked = self.repo.git.ls_files("--other","--exclude-standard").split()
        for f in self.modified:
            self.form.listFiles.addItem(f)
        for f in self.untracked:
            self.form.listFiles.addItem(f+" *")
        self.form.labelStatus.setText(translate("Arch","Branch")+": "+self.repo.active_branch.name)

    def getLog(self):
        textform = FreeCADGui.PySideUic.loadUi(":/ui/DialogDisplayText.ui")
        textform.setWindowTitle("Git log")
        textform.browserText.setPlainText(self.repo.git.log())
        textform.exec_()
        
    def getDiff(self):
        if (self.form.listFiles.currentRow() >= 0):
            f = (self.modified+self.untracked)[self.form.listFiles.currentRow()]
            textform = FreeCADGui.PySideUic.loadUi(":/ui/DialogDisplayText.ui")
            textform.setWindowTitle("Diff: "+f)
            textform.browserText.setPlainText(self.repo.git.diff(f))
            textform.exec_()
            
    def getRemotes(self):
        self.form.listRepos.clear()
        if self.repo.remotes:
            for r in self.repo.remotes:
                self.form.listRepos.addItem(r.name+": "+r.url)
        else:
            FreeCAD.Console.PrintWarning(translate("Arch","Warning: no remote repositories.\n"))
            
    def commit(self):
        if not self.form.listFiles.selectedItems():
            FreeCAD.Console.PrintError(translate("Arch","Please select file(s) to commit.\n"))
            self.form.labelStatus.setText(translate("Arch","No file selected"))
            return
        if not self.form.editMessage.text():
            FreeCAD.Console.PrintError(translate("Arch","Please write a commit message.\n"))
            self.form.labelStatus.setText(translate("Arch","No commit message"))
            return
        for it in self.form.listFiles.selectedItems():
            f = it.text()
            if f[-2:] == " *":
                f = f[:-2]
            self.repo.git.add(f)
        s = self.repo.git.commit(m=self.form.editMessage.text())
        FreeCAD.Console.PrintMessage(translate("Arch","Successfully committed %i files.\n") % len(self.form.listFiles.selectedItems()))
        self.form.labelStatus.setText(translate("Arch","Files committed."))
        if s:
            FreeCAD.Console.PrintMessage(s+"\n")
        self.getFiles()
        
    def push(self):
        if len(self.form.listRepos.selectedItems()) != 1:
            FreeCAD.Console.PrintError(translate("Arch","Please select a repo to push to.\n"))
            self.form.labelStatus.setText(translate("Arch","No repo selected"))
            return
        self.form.labelStatus.setText(translate("Arch","Pushing files..."))
        r = self.form.listRepos.selectedItems()[0].text().split(":")[0]
        s = self.repo.git.push(r)
        FreeCAD.Console.PrintMessage(translate("Arch","Successfully pushed to")+" "+r+"\n")
        self.form.labelStatus.setText(translate("Arch","Files pushed."))
        if s:
            FreeCAD.Console.PrintMessage(s+"\n")
        self.getFiles()
        
    def pull(self):
        if len(self.form.listRepos.selectedItems()) != 1:
            FreeCAD.Console.PrintError(translate("Arch","Please select a repo to pull from.\n"))
            self.form.labelStatus.setText(translate("Arch","No repo selected"))
            return
        self.form.labelStatus.setText(translate("Arch","Pulling files..."))
        r = self.form.listRepos.selectedItems()[0].text().split(":")[0]
        s = self.repo.git.pull(r)
        FreeCAD.Console.PrintMessage(translate("Arch","Successfully pulled from")+" "+r+"\n")
        self.form.labelStatus.setText(translate("Arch","Files pulled."))
        if s:
            FreeCAD.Console.PrintMessage(s+"\n")
        if os.path.basename(FreeCAD.ActiveDocument.FileName) in s:
            FreeCAD.Console.PrintWarning(translate("Arch","Warning: the current document file has been changed by this pull. Please save your document to keep your changes.\n"))



if FreeCAD.GuiUp:
    FreeCADGui.addCommand('Arch_Bimserver',_CommandBimserver())
    FreeCADGui.addCommand('Arch_Git',_CommandGit())
